package com.nulldev.util.web.HttpServer3.rewrite;

import static com.nulldev.util.web.HttpServer3.rewrite.InternalImpl.__should_chunk_transfer;
import static com.nulldev.util.web.HttpServer3.rewrite.InternalImpl.readRequest;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.KEEP_ALIVE_MAX_CONNS;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.KEEP_ALIVE_TIMEOUT;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.__apply_keep_alive_workarounds;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.__check_and_set_keepalive;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.__finish_stream;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.__guess_content_length;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.__sock_SO_SNDBUF;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.hasBlankResponse;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.isIgnorableException;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.isSocketClosed;
import static com.nulldev.util.web.HttpServer3.util.HttpServerUtil.stringForResponse;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URLConnection;
import java.nio.channels.WritableByteChannel;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;

import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.ArrayUtils.StringArrayUtils;
import com.nulldev.util.VariableAPI.ByteUtil;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.VariableAPI.util.strings.splitters.third_party.Splitter;
import com.nulldev.util.concurrency.threadIt.v4.asyncTasks.AsyncTaskCallback;
import com.nulldev.util.concurrency.threadIt.v4.emapi.cntft.ControllableFutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.variable;
import com.nulldev.util.data.CompressionAPI.thirdparty.airlift.snappy.SnappyFramedOutputStream;
import com.nulldev.util.data.CompressionAPI.thirdparty.airlift.zstd.ZstdOutputStream;
import com.nulldev.util.data.CompressionAPI.thirdparty.lzc.LZCOutputStream;
import com.nulldev.util.data.CompressionAPI.thirdparty.mig.MiGzOutputStream;
import com.nulldev.util.data.CompressionAPI.thirdparty.pgzip.ParallelGZIPOutputStream;
import com.nulldev.util.data.Sets.DataTriplet;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.extensions.copyable.Copyable;
import com.nulldev.util.io.extensions.copyable.nio.NIOCopyable;
import com.nulldev.util.io.util.TransferToBackport;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpServer3.ByteReadRange;
import com.nulldev.util.web.HttpServer3.CopyAcceleration;
import com.nulldev.util.web.HttpServer3.HttpCompressionMethod;
import com.nulldev.util.web.HttpServer3.HttpServer3.nCaptchaEnforcementMethod;
import com.nulldev.util.web.HttpServer3.HttpServer3Settings.HTTPSUpgradePolicy;
import com.nulldev.util.web.HttpServer3.MicroCache.CachedPage;
import com.nulldev.util.web.HttpServer3.Page;
import com.nulldev.util.web.HttpServer3.ReadRequest;
import com.nulldev.util.web.HttpServer3.Response;
import com.nulldev.util.web.HttpServer3.extensions.botAway.BotAway;
import com.nulldev.util.web.HttpServer3.extensions.crawlerBlock.CrawlerBlock;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.util.UUIDCookieLoader;
import com.nulldev.util.web.HttpServer3.extensions.sse.SSEPage;
import com.nulldev.util.web.HttpServer3.extensions.sse.SSESession;
import com.nulldev.util.web.HttpServer3.extensions.websocket.IWebSocketPage;
import com.nulldev.util.web.HttpServer3.extensions.websocket.Peer;
import com.nulldev.util.web.HttpServer3.extensions.websocket.ProtocolUtils;
import com.nulldev.util.web.HttpServer3.extensions.websocket.util.CloseReason;
import com.nulldev.util.web.HttpServer3.legacyPages.v1.HttpPage;
import com.nulldev.util.web.HttpServer3.legacyPages.v1.RequestType;
import com.nulldev.util.web.HttpServer3.legacyPages.v2.HttpPage2;
import com.nulldev.util.web.HttpServer3.pages.v3.ASyncHttpPage3;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3.CachingMethod;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPageRequestValidator;
import com.nulldev.util.web.HttpServer3.pages.v3.RequestProtocolType;
import com.nulldev.util.web.HttpServer3.rewrite.HttpServer3RW.ErrorType;
import com.nulldev.util.web.HttpServer3.util.ChunkedOutputStream;
import com.nulldev.util.web.HttpServer3.util.HTTPPrintWriter;
import com.nulldev.util.web.HttpServer3.util.HttpServerUtil.HTTPRequestPhase;
import com.nulldev.util.web.HttpServer3.util.ProxyBotStop;

class Http1Component {

	private static final Logger log = LoggerFactory.getLoggerDT(true);
	private static final Splitter QUERY_SPLIT = Splitter.onPattern("\\?|\\&");

	private final HttpServer3RW hs3;

	public Http1Component(final HttpServer3RW hs3) {
		this.hs3 = Variables.requireNonNullObject(hs3, "hs3");
	}

	private void __TIMING(final String str, final long stamp) {
		if (hs3.isTimingDebugEnabled)
			log.debug(str + " " + (System.currentTimeMillis() - stamp) + " ms");
	}

	private boolean __is_valid_request(final ReadRequest r, final RequestProtocolType type, final String method) {
		if (!r.header("Host") && type != RequestProtocolType.STTP_0_9) {
			/* HTTP requires a Host header. */
			return false;
		}
		if (!RequestType.isValidMethod(method)) {
			/* HttpServer doesn't understand non-standard methods. */
			return false;
		}
		return true;
	}

	private String __get_mime(final Page page) throws IOException {
		if (page instanceof HttpPage2) {
			return ((HttpPage2) page).getMimeType();
		} else if (page instanceof HttpPage) {
			if (((HttpPage) page).isCustomMime()) {
				return ((HttpPage) page).getMime();
			}
		} else if (page instanceof CachedPage) {
			final Page p = ((CachedPage) page).page();
			if (p instanceof HttpPage2) {
				return ((HttpPage2) p).getMimeType();
			} else if (p instanceof HttpPage) {
				if (((HttpPage) p).isCustomMime()) {
					return ((HttpPage) p).getMime();
				}
			}
		}
		return null;
	}

	/* */

	protected boolean handleHTTP(final String request0, final Socket socket, final InputStream is, final OutputStream os, final HTTPPrintWriter out,
			final long start, final boolean expectHTTPS, final variable<Boolean> __keep_alive) throws Exception {
		final String[] request = Splitters.splitString(request0, " ");
		if (request.length <= 1) {
			log.error("handle() -> Invalid request! [data=" + ByteUtil.getHex(request0.getBytes()) + "]");
			IOUtils.closeAllQuietly(out, is, socket);
			__keep_alive.set(false);
			return true;
		}
		final String REQUEST_TYPE = request[0];
		String REQUEST_URI = "/";
		try {
			REQUEST_URI = QUERY_SPLIT.splitToArray(request[1])[0];
		} catch (Exception e) {
			log.error("Failed to get REQUEST_URI!", e);
		}
		String requestVersion = "HTTP/1.0";
		try {
			requestVersion = request[2];
		} catch (Exception e) {
			log.error("Failed to get REQUEST_VERSION! [r0: " + request0 + "]", e);
		}
		RequestProtocolType protocol = RequestProtocolType.guessProtocolFromVersion(requestVersion, expectHTTPS || socket instanceof SSLSocket);
		__TIMING("handle::request_identify:", start);
		if (hs3.settings.enableProxyBotStop && ProxyBotStop.isProxyBot(REQUEST_URI)) {
			switch (hs3.settings.proxyBotStopMode) {
				case DROP_CONNECTION: {
					log.warn("[ProxyBotStop] Dropped connection: " + socket);
					__keep_alive.set(false);
					return true;
				}
				case REDIRECT_TO_CF: {
					out.println("HTTP/1.1 301 Moved Permanently");
					out.println("Server: HttpServer3 from nullUtil 3.0");
					out.println("Location: https://www.cloudflare.com/");
					out.println("Connection: close");
					out.println();
					out.println(
							"<html><head>\n" + "<meta http-equiv=\"Refresh\" content=\"0; URL=https://www.cloudflare.com/\" />\n" + "</head>\n" + "</html>");
					out.flush();
					__keep_alive.set(false);
					return true;
				}
				default:
					break;
			}
		}
		if (hs3.settings.enableBotAway && BotAway.isBot(REQUEST_URI)) {
			log.warn("[BotAway] Dropped connection: " + socket);
			__keep_alive.set(false);
			return true;
		}
		final ReadRequest rr = readRequest(hs3, socket, is);
		__TIMING("handle::request_read:", start);
		if (!__is_valid_request(rr, protocol, REQUEST_TYPE)) {
			hs3._invoke_error(is, out, socket, ErrorType.ERROR_400);
			return false;
		}
		if (hs3.settings.debugPrintRequests) {
			log.info("\t" + REQUEST_TYPE + " [" + (rr.hashCode()) + "]");
			log.info("\t" + request[1]);
			log.info("\t" + requestVersion);
			for (final Map.Entry<String, String> header : rr.headers().entrySet()) {
				log.info("\t\t" + header.getKey() + ": " + header.getValue(), true);
			}
		}
		// WebSocket Entrypoint
		if (rr.header("Upgrade") && rr.headerIs("Upgrade", "websocket") && hs3.flags.enable_ws) {
			final Page p = hs3.getPage(REQUEST_URI, socket);
			if (p == null) {
				hs3._invoke_error(is, out, socket, ErrorType.ERROR_404);
				return false;
			} else if (!(p instanceof IWebSocketPage)) {
				hs3._invoke_error(is, out, socket, ErrorType.ERROR_400);
				return false;
			} else {
				final IWebSocketPage iws = (IWebSocketPage) p;
				log.debug("handle::WS_UPGRADE: " + rr.header("Upgrade"));
				if (iws.prefersLowLatency()) {
					try {
						socket.setTcpNoDelay(true);
					} catch (Exception ex) {
					}
				}
				final String key = rr.headers().get("Sec-WebSocket-Key");
				out.println("HTTP/1.1 101 Switching Protocols");
				out.println("Connection: Upgrade");
				out.println("Upgrade: websocket");
				out.println("Sec-WebSocket-Accept: " + ProtocolUtils.getMagicPayload(key));
				if (iws.hasProtocolName()) {
					out.print("Sec-WebSocket-Protocol: ");
					out.println(iws.getProtocolName());
				}
				out.println("Server: HttpServer3 from nullUtil 3.0");
				out.println();
				out.flush();
				final Peer peer = new Peer(socket, is, os, rr.headers()) {
					@Override
					public void onConnected() {
						iws.onPeerConnect(this);
					}

					@Override
					public void onBinaryMessage(byte[] message) {
						iws.onBinaryMessage(this, message);
					}

					@Override
					public void onTextMessage(String message) {
						iws.onTextMessage(this, message);
					}

					@Override
					public void onClose(final CloseReason reason) {
						iws.onPeerClose(this, reason);
					}
				};
				peer.run();
				return false;
			}
		} else if (rr.header("Accept") && rr.headerIs("Accept", "text/event-stream") && hs3.flags.enable_sse) {
			final Page p = hs3.getPage(REQUEST_URI, socket);
			if (p == null || !(p instanceof SSEPage)) {
				hs3._invoke_error(is, out, socket, ErrorType.ERROR_404);
				return false;
			}
			final SSEPage s = (SSEPage) p;
			if (s.prefersLowLatency()) {
				try {
					socket.setTcpNoDelay(true);
				} catch (Exception ex) {
				}
			}
			log.debug("handle::SSE_UPGRADE: " + rr.header("Accept"));
			out.println("HTTP/1.1 200 OK");
			out.println("Server: HttpServer3 from nullUtil 3.0");
			out.println("Connection: keep-alive");
			out.println("Content-Type: text/event-stream; charset=utf-8");
			out.println();
			out.flush();
			final SSESession sse = new SSESession(socket, is, os, out, rr, REQUEST_URI) {

				@Override
				public void onConnected(final ReadRequest rr, final String url) {
					s.onConnected(this, rr, url);
				}

			};
			try {
				sse.run();
				IOUtils.closeQuietly(sse);
			} catch (Exception e) {
				log.error("[SSE::\"" + s + "\"] Failed to run session!", e);
				s.onDisconnect(sse);
				IOUtils.closeQuietly(sse);
			}
			// SSE Entrypoint
			return false;
		} else if ((rr.header("Upgrade") || (rr.header("HTTP2-Settings"))) && rr.headerIs("Upgrade", "h2c") && hs3.flags.enable_http2) {
			// HTTP 2 Entrypoint
			log.debug("handle::HTTP2_UPGRADE: " + rr.header("Upgrade"));
			protocol = hs3.h2c.__upgrade_to_http2(out, os, is, socket, protocol, expectHTTPS);
			return false;
		} else {
			log.debug("request0: " + request0);
			return this.postInit(request0, socket, is, os, out, start, __keep_alive, rr, requestVersion, REQUEST_URI, REQUEST_TYPE, request, protocol,
					expectHTTPS);
		}
	}

	private boolean postInit(final String request0, final Socket socket, final InputStream is, final OutputStream os, final HTTPPrintWriter out,
			final long start, final variable<Boolean> __keep_alive, final ReadRequest rr, final String requestVersion, final String REQUEST_URI,
			final String REQUEST_TYPE, final String[] request, final RequestProtocolType protocol, final boolean expectHTTPS) throws Exception {
		final boolean promptHTTPSUpgrade = !expectHTTPS && hs3.settings.enableHTTPSUpgrade
				&& hs3.settings.httpsUpgradePolicy == HTTPSUpgradePolicy.VIA_UPGRADE_INSECURE_REQUESTS && hs3.httpsPort == 443;
		if (promptHTTPSUpgrade && rr.headerIs("Upgrade-Insecure-Requests", "1") && hs3.settings.httpsUpgradeDomain != null) {
			out.println("HTTP/1.1 307 Moved Temporarily");
			out.println("Location: " + hs3.settings.httpsUpgradeDomain);
			out.println("Vary: Upgrade-Insecure-Requests");
			out.println("Server-name: " + hs3.settings.getServerName());
			out.println("Server: HttpServer3 from nullUtil 3.0");
			out.println("Cache-Control: no-store");
			out.println();
			__keep_alive.set(false);
			return true;
		}
		final Map<String, String> headers = rr.headers();
		if (hs3.settings.enableCrawlerBlock && CrawlerBlock.isCrawler(headers.getOrDefault("User-Agent", "null"))) {
			__keep_alive.set(false);
			return true;
		}
		__check_and_set_keepalive(protocol, __keep_alive, headers);
		__TIMING("handle::full_request:", start);
		Page _page = hs3.getPage(REQUEST_URI, socket);
		final boolean exclude_from_captcha = (_page instanceof HttpPage3) && (((HttpPage3) _page).isExcludedFromCaptcha());
		__TIMING("handle::getPage:", start);

		if (hs3.settings.enableNCaptcha) {
			final boolean valid_captcha = UUIDCookieLoader.validateFromHeaders(headers);
			if (!exclude_from_captcha && !valid_captcha && !REQUEST_URI.startsWith("/captcha/")
					&& hs3.settings.nCaptchaMethod == nCaptchaEnforcementMethod.PRE_CONNECT) {
				if (_page instanceof HttpPage3 && ((HttpPage3) _page).shouldUseJSONCaptchaPage()) {
					_page = hs3.getPage("/captcha-json", socket);
				} else {
					_page = hs3.getPage("/captcha", socket);
				}
			}
		}

		final Page page = _page;
		if (page == null) {
			log.debug("[invoke_error] Cause: PAGE_NOT_FOUND");
			hs3._invoke_error(is, out, socket, ErrorType.ERROR_404);
			return true;
		}
		if (page instanceof IWebSocketPage) {
			log.debug("[invoke_error] Cause: INVALID_REQUEST (WEBSOCKET)");
			hs3._invoke_error(is, out, socket, ErrorType.ERROR_400);
			return true;
		}
		__TIMING("handle::page_ready:", start);
		final RequestType type = RequestType.fetch(REQUEST_TYPE);
		__TIMING("handle::page_pre_validate:", start);
		if (page instanceof HttpPage3) {
			if (HttpPageRequestValidator.checkSecurity(type, rr, ((HttpPage3) page))) {
				log.debug("[invoke_error] Cause: SECURITY_CHECK_FAILED");
				hs3._invoke_error(is, out, socket, ErrorType.ERROR_SECURITY_CHECK);
				__keep_alive.set(false);
				return false;
			}
		}
		__TIMING("handle::page_pre_exec:", start);
		try {
			// Response Code, ResponseStream, Response Headers
			if (page instanceof ASyncHttpPage3) { // use new logic.
				final ControllableFutureTask<Response> r = new ControllableFutureTask<Response>();
				final HttpPage3.Request ar = new HttpPage3.Request(request[1], rr.hasStream() ? rr.stream() : HttpPage3.BLANK_IO_STREAM,
						socket.getInetAddress().getHostAddress(), protocol, socket);
				__TIMING("handle::async_request_make:", start);
				if (hs3.flags.enable_queued_requests) {
					hs3.td.asyncSubmit(() -> {
						((ASyncHttpPage3) page).onFutureRequest(ar.importHeaders(headers).setRequestType(type), r);
					});
				} else {
					((ASyncHttpPage3) page).onFutureRequest(ar.importHeaders(headers).setRequestType(type), r);
				}
				__TIMING("handle::async_request_call:", start);
				r.addCallback(new AsyncTaskCallback<Response>() {

					@Override
					public void onCompletion(Response result) {
						__TIMING("handle::async_response:", start);
						try {
							Http1Component.this.handleRespondedPage(result, page, __keep_alive, is, os, out, socket, headers, ar, start, protocol, expectHTTPS);
						} catch (Exception e) {
							__keep_alive.set(false);
							IOUtils.closeAllQuietly(is, os, out, socket, ar);
							if (e instanceof SocketTimeoutException)
								log.warn("Encountered a socket timeout, closing connection.");
							else if (e instanceof SocketException || e.getMessage().contains("Broken pipe (Write failed)"))
								log.warn("Encountered a socket error, closing connection.");
							else
								throw new RuntimeException(e);
						}
					}

					@Override
					public void onError(Throwable e) {
						throw new RuntimeException(e);
					}
				});
				if (rr.hasTimeoutDuration() && rr.appliesTimeoutTo("request")) {
					r.waitFor(rr.getTimeoutDuration().toMillis(), TimeUnit.MILLISECONDS);
				} else {
					r.waitTillCompletion();
				}
				if (!r.completed()) {
					r.kill();
					__keep_alive.set(false);
					IOUtils.closeAllQuietly(is, os, out, socket);
					return false;
				} else {
					return !__keep_alive.get();
				}
			} else {
				if (hs3.flags.enable_queued_requests) {
					final Executable<?> x = hs3.td.asyncSubmit(() -> {
						try {
							__TIMING("handle::queued_entry:", start);
							try {
								final Response response = this.do_response(type, request, rr, socket, headers, page, protocol);
								__TIMING("handle::do_response_queued:", start);
								this.handleRespondedPage(response, page, __keep_alive, is, os, out, socket, headers, null, start, protocol, expectHTTPS);
								return null;
							} catch (Exception e) {
								__keep_alive.set(false);
								IOUtils.closeAllQuietly(is, os, out, socket);
								if (e instanceof SocketTimeoutException) {
									log.warn("Encountered a socket timeout, closing connection.");
									return null;
								} else
									throw e;
							}
						} catch (Throwable e) {
							throw new RuntimeException(e);
						}
					});
					if (rr.hasTimeoutDuration() && rr.appliesTimeoutTo("request")) {
						x.waitFor(rr.getTimeoutDuration().toMillis(), TimeUnit.MILLISECONDS);
					} else {
						x.waitTillCompletion();
					}
					if (!x.completed())
						x.kill();
					return !__keep_alive.get();
				} else {
					if (hs3.isTimingDebugEnabled) {
						log.debug("handle::entry: " + ((System.currentTimeMillis() - start) + " ms"));
					}
					try {
						final Response response = this.do_response(type, request, rr, socket, headers, page, protocol);
						__TIMING("handle::do_response:", start);
						this.handleRespondedPage(response, page, __keep_alive, is, os, out, socket, headers, null, start, protocol, expectHTTPS);
						return !__keep_alive.get();
					} catch (Exception e) {
						__keep_alive.set(false);
						IOUtils.closeAllQuietly(is, os, out, socket);
						log.error("Response failed!", e);
						return false;
					}
				}
			}
		} catch (Exception e) {
			if (isIgnorableException(e)) {
				log.debug("[invoke_error] Cause: ERROR_IS_IGNORABLE");
			} else {
				log.error("handle() -> Failed to process client!", e);
			}
			hs3._invoke_error(is, out, socket, ErrorType.ERROR_503);
			IOUtils.closeAllQuietly(is, out, os, socket);
			return true;
		}
	}

	private static final variable<Boolean> LOCKED_FALSE = variable.makeLocked(false);

	private void handleRespondedPage(final Response response, final Page page, final variable<Boolean> __keep_alive, final InputStream is,
			final OutputStream os, final HTTPPrintWriter out, final Socket socket, final Map<String, String> headers, final HttpPage3.Request ar,
			final long start, final RequestProtocolType protocol, final boolean expectHTTPS) throws Exception {
		try {
			__TIMING("handle::handleRespondedPage:", start);
			if (!hs3.isValid(response)) {
				log.debug("[invoke_error] Cause: INVALID_RESPONSE");
				hs3._invoke_error(is, out, socket, ErrorType.ERROR_BAD_RESPONSE);
				__keep_alive.set(false);
				return;
			}
			__TIMING("handle::post_valid_check:", start);
			if (response.shouldDisableKeepAlive())
				__keep_alive.set(false);
			HttpCompressionMethod comp = HttpCompressionMethod.parse(headers.get("Accept-Encoding"));
			if (comp.equals(HttpCompressionMethod.GZIP) && !hs3.flags.responses_enable_gzip_encoding) {
				comp = HttpCompressionMethod.NONE;
				__keep_alive.set(false);
			}
			__TIMING("handle::response_ready:", start);
			final boolean ignoreCompressionWorkaround = hs3.flags.enable_compressed_chunked_transfers && hs3.flags.enable_chunked_transfers;
			if (response.shouldDeferCompression() || comp == null)
				comp = HttpCompressionMethod.NONE;
			final boolean __enable_chunk_read = __do_response_headers(response, out, start, __keep_alive, ignoreCompressionWorkaround, comp, page, expectHTTPS,
					headers);
			this.__write_to_socket(socket, comp, headers, out, response, is, os, page, response.shouldDisableKeepAlive() ? LOCKED_FALSE : __keep_alive, hs3.ccm,
					start, __enable_chunk_read, protocol);
			__TIMING("handle::__write_to_socket:", start);
			IOUtils.closeAllQuietly(response, ar);
		} catch (Exception e) {
			IOUtils.closeAllQuietly(is, response, ar, socket, os, out);
			__keep_alive.set(false);
			if (e instanceof SocketException || (e.getMessage() != null && e.getMessage().contains("Broken pipe"))
					|| (e.getMessage() != null && e.getMessage().contains("timeout"))) {
				log.derror("handleRespondedPage() -> Request write failed, cancelling response. [error: " + e + "]");
			} else
				throw new RuntimeException(e);
		}
	}

	private boolean __do_response_headers(final Response response, final HTTPPrintWriter out, final long start, final variable<Boolean> __keep_alive,
			final boolean ignoreCompressionWorkaround, final HttpCompressionMethod comp, final Page page, final boolean expectHTTPS,
			final Map<String, String> headers) throws IOException {
		if (hs3.flags.enable_early_hints && response.hasPreloads()) {
			log.debug("handle::pre_early_hints: " + ((System.currentTimeMillis() - start) + " ms"));
			out.println("HTTP/1.1 103 Early Hints");
			for (final Map.Entry<String, String> preload : response.preloads().entrySet()) {
				out.println("Link: " + preload.getKey() + "; rel=preload; as=" + preload.getValue());
			}
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			log.debug("handle::early_hints: " + ((System.currentTimeMillis() - start) + " ms"));
		}
		final int resp_code = response.getA();
		out.println("HTTP/1.1 " + resp_code + " " + stringForResponse(resp_code));
		out.println("Server-name: " + hs3.settings.getServerName());
		out.println("Server: HttpServer3 from nullUtil 3.0");
		if (hs3.flags.opt_out_floc)
			out.println("Permissions-Policy: interest-cohort=()");
		if (hs3.flags.requests_enable_byte_ranges) {
			out.println("Accept-Ranges: bytes");
		}
		if (response.getCustomHeaders() != null && !response.getCustomHeaders().isEmpty()) {
			if (hs3.settings.debugPrintResponses) {
				log.info("\tRESPONSE");
				for (final Map.Entry<String, String> header : response.getCustomHeaders().entrySet()) {
					log.info("\t\t" + header.getKey() + ": " + header.getValue(), true);
				}
			}
			for (final Map.Entry<String, String> e : response.getCustomHeaders().entrySet()) {
				out.println(e.getKey() + ": " + e.getValue());
			}
		}
		__apply_keep_alive_workarounds(response, ignoreCompressionWorkaround, comp, __keep_alive);
		String mime = __get_mime(page);
		if (response.hasCustomMime()) {
			out.println("Content-Type: " + response.getCustomMime());
		} else if (!response.copyable() && !response.nioCopyable() && response.getInputStream().markSupported()) {
			if (mime == null) {
				try {
					mime = URLConnection.guessContentTypeFromStream(response.getB());
				} catch (Exception e) {
				}
			}
			if (mime != null) {
				out.println("Content-Type: " + mime);
			}
		}
		__apply_caching_headers(page, out);
		if (hs3.settings.allowCORSFromExternals) {
			out.println("Access-Control-Allow-Origin: *");
			out.println("Access-Control-Allow-Credentials: true");
			out.println("Access-Control-Allow-Headers: *");
		}
		if (hs3.settings.enableHSTS) {
			if (hs3.settings.enableHSTSForSubdomains) {
				out.println("Strict-Transport-Security: max-age=31536000; includeSubDomains");
			} else {
				out.println("Strict-Transport-Security: max-age=31536000");
			}
		}
		final boolean promptForHTTPS = hs3.httpsServerSocket != null && !expectHTTPS && hs3.httpsPort == 443;
		if (hs3.settings.enableHTTPSUpgrade && hs3.settings.httpsUpgradePolicy == HTTPSUpgradePolicy.VIA_CSP && promptForHTTPS) {
			out.println("Content-Security-Policy: upgrade-insecure-requests");
		}
		if (hs3.flags.enable_http2) {
			out.println("Alt-Svc: h2=\":" + (hs3.httpsServerSocket != null ? hs3.httpsPort : hs3.port) + "\"; ma=2592000;");
		}
		if (hs3.settings.hasCORS() && !response.hasHeader("Cross-Origin-Resource-Policy")) {
			out.println("Cross-Origin-Resource-Policy: " + hs3.settings.corsPolicy.value());
		}
		if (hs3.settings.hasCOEP() && !response.hasHeader("Cross-Origin-Embedder-Policy")) {
			out.println("Cross-Origin-Embedder-Policy: " + hs3.settings.coepPolicy.value());
		}
		if (hs3.settings.hasCOOP() && !response.hasHeader("Cross-Origin-Opener-Policy")) {
			out.println("Cross-Origin-Opener-Policy: " + hs3.settings.coopPolicy.value());
		}
		boolean __enable_chunk_read = headers.containsKey("Range") && hs3.flags.requests_enable_byte_ranges;
		if (page instanceof HttpPage2) {
			if (__enable_chunk_read && !((HttpPage2) page).allowRanges()) {
				__enable_chunk_read = false;
			}
		}
		if (hs3.flags.response_guess_content_length) {
			__guess_content_length(comp, response, out, __enable_chunk_read);
		} else if (hasBlankResponse(response)) {
			out.println("Content-Length: 0");
		} else {
			if (!response.__has_header("Content-Length") && !hs3.flags.enable_chunked_transfers && !hs3.flags.enable_compressed_chunked_transfers) {
				/* Disable Keep-Alive on responses where we don't know the length. */
				__keep_alive.set(false);
			}
		}

		return __enable_chunk_read;
	}

	private void __apply_caching_headers(final Page page, final HTTPPrintWriter out) {
		if (page instanceof HttpPage3 && ((HttpPage3) page).isCaching()) {
			if (!((HttpPage3) page).getCachingMethod().equals(CachingMethod.BLANK)) {
				out.println("Cache-Control: " + ((HttpPage3) page).getCachingMethod().getStringValue());
				if (((HttpPage3) page).getCachingMethod().equals(CachingMethod.NO_CACHE) && hs3.settings.enableRFC9111Compat) {
					out.println("Pragma: no-cache");
				}
			}
		} else if (page instanceof CachedPage && ((CachedPage) page).page() instanceof HttpPage3) {
			if (((HttpPage3) ((CachedPage) page).page()).isCaching()) {
				if (!((HttpPage3) ((CachedPage) page).page()).getCachingMethod().equals(CachingMethod.BLANK)) {
					out.println("Cache-Control: " + ((HttpPage3) ((CachedPage) page).page()).getCachingMethod().getStringValue());
					if (((HttpPage3) ((CachedPage) page).page()).getCachingMethod().equals(CachingMethod.NO_CACHE) && hs3.settings.enableRFC9111Compat) {
						out.println("Pragma: no-cache");
					}
				}
			}
		}
	}

	@SuppressWarnings("resource")
	private Response do_response(final RequestType type, final String[] request, final ReadRequest rr, final Socket socket, final Map<String, String> headers,
			final Page page, final RequestProtocolType protocol) throws IOException {
		if (page instanceof HttpPage3) {
			final HttpPage3.Request req = new HttpPage3.Request(request[1], rr.hasStream() ? rr.stream() : HttpPage3.BLANK_IO_STREAM,
					socket.getInetAddress().getHostAddress(), protocol, socket).importHeaders(headers).setRequestType(type);
			return ((HttpPage3) page).onRequest(req);
		} else if (page instanceof HttpPage2) {
			final byte[] extraData = rr.hasStream() ? IOUtils.toByteArray(rr.stream()) : null;
			return new Response(((HttpPage2) page).request(type, request[1], rr.headers_legacy(), extraData, socket.getInetAddress().getHostAddress()));
		} else if (page instanceof HttpPage) {
			final HttpPage p = ((HttpPage) page);
			if (p.isOverBytes()) {
				return new Response(new DataTriplet<Integer, InputStream, Map<String, String>>(200,
						new ByteArrayInputStream(p.handleRequest_Alt(request[1], socket.getInetAddress().getHostAddress(), type)),
						new TreeMap<String, String>()));
			} else if (p.sendRaw()) {
				return new Response(
						new DataTriplet<Integer, InputStream, Map<String, String>>(200, new FileInputStream(p.getPath()), new TreeMap<String, String>()));
			} else {
				return new Response(new DataTriplet<Integer, InputStream, Map<String, String>>(200,
						new ByteArrayInputStream(p.handleRequest(request[1], socket.getInetAddress().getHostAddress(), type).getBytes()),
						new TreeMap<String, String>()));
			}
		} else if (page instanceof CachedPage) {
			final CachedPage cp = (CachedPage) page;
			return new Response(200, cp, cp);
		} else {
			return null;
		}
	}

	private void __write_to_socket(final Socket socket, HttpCompressionMethod comp, final Map<String, String> headers, final HTTPPrintWriter out,
			final Response response, final InputStream is, final OutputStream _os, final Page page, final variable<Boolean> __keep_alive,
			final CopyAcceleration ccm, final long start, final boolean __enable_chunk_read, final RequestProtocolType protocol) throws Exception {
		/* Send Connection header */
		if (__keep_alive.get()) {
			out.println("Connection: keep-alive");
			out.println("Keep-Alive: timeout=" + KEEP_ALIVE_TIMEOUT + ", max=" + KEEP_ALIVE_MAX_CONNS);
		} else {
			out.println("Connection: close");
		}

		/* Chunking */
		final boolean shouldUseChunked = __should_chunk_transfer(hs3, response, protocol);
		if (shouldUseChunked) {
			out.println("Transfer-Encoding: chunked");
		}

		if (hs3.flags.enable_early_socket_close && protocol.isHTTP()) {
			IOUtils.closeInputQuietly(socket);
		}

		__TIMING("__write_to_socket::post-flags:", start);
		if (isSocketClosed(socket, HTTPRequestPhase.DURING_RESPONSE)) {
			log.debug("__write_to_socket: Socket is closed, bailing out.");
			IOUtils.closeAllQuietly(is, out, socket, response);
			__keep_alive.set(false);
			return;
		} else {
			ByteReadRange[] byteRanges = null;
			if (__enable_chunk_read) {
				final String _range = headers.get("Range");
				final String[] _core_values = Splitters.splitString(_range, "=");
				final String unit = _core_values[0];
				if (!unit.equalsIgnoreCase("bytes")) {
					log.warn("Unknown range delivery type. [got=" + unit + "]");
				} else {
					final String[] ranges = Splitters.splitString(StringArrayUtils.addFrom(_core_values, 1, ""), ",");
					for (final String range : ranges) {
						final String[] rangeData = Splitters.splitString(range, "-");
						if (rangeData.length > 2) {
							log.warn("Received a larger than normal range. Skipping.");
							continue;
						}
						long min = -1;
						if (rangeData.length >= 1) {
							min = MathUtil.safeParseL(rangeData[0], -1);
						}
						long max = -1;
						if (rangeData.length == 2) {
							max = MathUtil.safeParseL(rangeData[1], -1);
						}
						if (byteRanges == null) {
							byteRanges = new ByteReadRange[]
								{ new ByteReadRange(min, max) };
						} else {
							byteRanges = ArrayUtils.add(byteRanges, new ByteReadRange(min, max));
						}
						log.debug("Reading range: " + new ByteReadRange(min, max));
					}
				}
			}
			final boolean __has_chunks = __enable_chunk_read && byteRanges != null && byteRanges.length > 0;
			if (__has_chunks) {
				out.println("Content-Length: " + byteRanges[0].length());
			}
			final OutputStream os = get_os(response, socket, out, _os, comp, shouldUseChunked);
			__TIMING("__write_to_socket::os:", start);

			IOUtils.flushQuietly(_os);

			__TIMING("__write_to_socket::os_flushed:", start);
			if (response.copyable()) {
				__copyable(shouldUseChunked, response, os, _os, comp, socket, __keep_alive, out, is, start);
			} else if (response.nioCopyable()) {
				__nio_copyable(shouldUseChunked, response, os, _os, comp, socket, __keep_alive, out, is, start);
			} else if (os == null) {
				__legacy_copy(__has_chunks, byteRanges, response, os, _os, comp, shouldUseChunked, __keep_alive, socket, out, is, start);
			} else {
				__fallback_copy(__has_chunks, byteRanges, response, os, shouldUseChunked, comp, __keep_alive, socket, out, _os, is, start);
			}

			IOUtils.flushQuietly(os != null ? os : _os);
			IOUtils.closeQuietly(response);

			/* Sockets are closed by the handle() function */
		}
	}

	private OutputStream get_os(final Response response, final Socket socket, final HTTPPrintWriter out, final OutputStream os, HttpCompressionMethod comp,
			final boolean shouldUseChunked) throws IOException {
		if ((hs3.flags.enable_chunked_transfers || response.shouldPreferChunking()) && shouldUseChunked) {
			switch (comp) {
				case NONE: {
					break;
				}
				default:
					out.println("Content-Encoding: " + comp.getResponseValue());
					break;
			}
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			return new ChunkedOutputStream(IOUtils.nonCloseable(os));
		} else if (response.isNativeEncoded.get() && response.nativeEncoding != null && !response.nativeEncoding.isEmpty()) {
			if (!response.nativeEncoding.equalsIgnoreCase("none")) {
				out.println("Content-Encoding: " + response.nativeEncoding);
				out.println();
				if (!hs3.flags.reduce_response_flushing)
					out.flush();
			} else {
				out.println();
				if (!hs3.flags.reduce_response_flushing)
					out.flush();
			}
			return os;
		} else if (comp == HttpCompressionMethod.DEFLATE) {
			out.println("Content-Encoding: deflate");
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			return new DeflaterOutputStream(os, new Deflater(9, true));
		} else if (comp == HttpCompressionMethod.GZIP && hs3.flags.responses_enable_gzip_encoding) {
			out.println("Content-Encoding: gzip");
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			if (hs3.flags.use_migz_for_gzip)
				return new MiGzOutputStream(os);
			else if (hs3.flags.use_parallel_gzip)
				return new ParallelGZIPOutputStream(os);
			else
				return new GZIPOutputStream(os, __sock_SO_SNDBUF(socket), true);
		} else if (comp == HttpCompressionMethod.COMPRESS) {
			out.println("Content-Encoding: compress");
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			return new LZCOutputStream(os);
		} else if (comp == HttpCompressionMethod.ZSTD_NO_DICT) {
			out.println("Content-Encoding: zstd");
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			return new ZstdOutputStream(os);
		} else if (comp == HttpCompressionMethod.SNAPPY) {
			out.println("Content-Encoding: x-snappy-framed");
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			return new SnappyFramedOutputStream(os);
		} else {
			out.println();
			if (!hs3.flags.reduce_response_flushing)
				out.flush();
			return null;
		}
	}

	private void __do_chunked_transfer(final InputStream r, final OutputStream o, final HttpCompressionMethod method) throws IOException {
		try {
			if (method == null || method == HttpCompressionMethod.NONE) {
				copy(hs3.ccm, r, o);
				((ChunkedOutputStream) o).writeEOF();
			} else {
				OutputStream ob;
				switch (method) {
					case GZIP: {
						if (hs3.flags.use_migz_for_gzip)
							ob = new MiGzOutputStream(o);
						else if (hs3.flags.use_parallel_gzip)
							ob = new ParallelGZIPOutputStream(o);
						else
							ob = new GZIPOutputStream(o);
						break;
					}
					case DEFLATE: {
						ob = new DeflaterOutputStream(o, new Deflater(9, true));
						break;
					}
					case COMPRESS: {
						ob = new LZCOutputStream(o);
						break;
					}
					case ZSTD_NO_DICT: {
						ob = new ZstdOutputStream(o);
						break;
					}
					case SNAPPY: {
						ob = new SnappyFramedOutputStream(o);
						break;
					}
					default:
						throw new UnsupportedOperationException("Cannot chunk: " + method);
				}
				copy(hs3.ccm, r, ob);
				try {
					__finish_stream(ob);
					ob.flush();
				} catch (Exception n) {
				}
				if (!hs3.flags.reduce_response_flushing)
					o.flush();
				((ChunkedOutputStream) o).writeEOF();
				IOUtils.closeAllQuietly(ob);
			}
		} catch (Exception e) {
			if (!isIgnorableException(e))
				log.error("__do_chunked_transfer: Connection failed?", e);
		}
	}

	private void __do_chunked_copyable(final Copyable r, final OutputStream o, final HttpCompressionMethod method) throws IOException {
		try {
			if (method == null || method == HttpCompressionMethod.NONE) {
				r.writeTo(o);
				((ChunkedOutputStream) o).writeEOF();
			} else {
				OutputStream ob;
				switch (method) {
					case GZIP: {
						if (hs3.flags.use_migz_for_gzip) {
							ob = new MiGzOutputStream(o);
						} else if (hs3.flags.use_parallel_gzip) {
							ob = new ParallelGZIPOutputStream(o);
						} else {
							ob = new GZIPOutputStream(o);
						}
						break;
					}
					case DEFLATE: {
						ob = new DeflaterOutputStream(o, new Deflater(9, true));
						break;
					}
					case COMPRESS: {
						ob = new LZCOutputStream(o);
						break;
					}
					case ZSTD_NO_DICT: {
						ob = new ZstdOutputStream(o);
						break;
					}
					case SNAPPY: {
						ob = new SnappyFramedOutputStream(o);
						break;
					}
					default:
						throw new UnsupportedOperationException("Cannot chunk: " + method);
				}
				r.writeTo(ob);
				try {
					__finish_stream(ob);
					ob.flush();
				} catch (Exception n) {
				}
				((ChunkedOutputStream) o).writeEOF();
				IOUtils.closeAllQuietly(ob);
			}
			if (!hs3.flags.reduce_response_flushing)
				o.flush();
		} catch (Exception e) {
			if (!isIgnorableException(e))
				log.error("__do_chunked_copyable: Connection failed?", e);
		}
	}

	private void __do_chunked_nio_copyable(final NIOCopyable r, final OutputStream _os, final WritableByteChannel wbc, final HttpCompressionMethod method)
			throws IOException {
		try {
			if (method == null || method == HttpCompressionMethod.NONE) {
				final WritableByteChannel wbc_compat = IOUtils.copyMakeChannel(_os);
				r.writeTo(wbc_compat);
				((ChunkedOutputStream) _os).writeEOF();
			} else {
				OutputStream ob;
				switch (method) {
					case GZIP: {
						if (hs3.flags.use_migz_for_gzip)
							ob = new MiGzOutputStream(_os);
						else if (hs3.flags.use_parallel_gzip)
							ob = new ParallelGZIPOutputStream(_os);
						else
							ob = new GZIPOutputStream(_os);
						break;
					}
					case DEFLATE: {
						ob = new DeflaterOutputStream(_os, new Deflater(9, true));
						break;
					}
					case COMPRESS: {
						ob = new LZCOutputStream(_os);
						break;
					}
					case ZSTD_NO_DICT: {
						ob = new ZstdOutputStream(_os);
						break;
					}
					case SNAPPY: {
						ob = new SnappyFramedOutputStream(_os);
						break;
					}
					default:
						throw new UnsupportedOperationException("Cannot chunk: " + method);
				}
				final WritableByteChannel wbc_compat = IOUtils.copyMakeChannel(ob);
				r.writeTo(wbc_compat);
				try {
					__finish_stream(ob);
					ob.flush();
				} catch (Exception n) {
				}
				((ChunkedOutputStream) _os).writeEOF();
				IOUtils.closeAllQuietly(ob, wbc_compat);
			}
		} catch (Exception e) {
			if (!isIgnorableException(e))
				log.error("__do_chunked_nio_copyable: Connection failed?", e);
		}
	}

	private void __do_byte_ranges(final ByteReadRange[] byteRanges, final InputStream r, final OutputStream os) throws IOException {
		int previousMin = -1;
		for (final ByteReadRange brr : byteRanges) {
			final boolean wasPreviousBad = (previousMin == -1);
			byte[] disposableBuffer = new byte[(int) brr.min];
			previousMin = (int) brr.min;
			if (brr.min <= 0) {
				// no read required;
			} else {
				if (!wasPreviousBad) {
					disposableBuffer = new byte[(int) (brr.min - previousMin)];
					r.read(disposableBuffer);
				} else {
					r.read(disposableBuffer);
				}
			}
			disposableBuffer = new byte[(int) brr.toRead()];
			r.read(disposableBuffer);
			os.write(disposableBuffer);
		}
	}

	private void __nio_copyable(final boolean shouldUseChunked, final Response response, final OutputStream os, final OutputStream _os,
			final HttpCompressionMethod comp, final Socket socket, final variable<Boolean> __keep_alive, final HTTPPrintWriter out, final InputStream is,
			final long start) throws Exception {
		try {
			if (shouldUseChunked) {
				this.__do_chunked_nio_copyable(response.getNIOCopyable(), (os != null ? os : _os), IOUtils.copyMakeChannel(os != null ? os : _os), comp);
			} else {
				response.getNIOCopyable().writeTo(IOUtils.copyMakeChannel(os != null ? os : _os));
			}
		} catch (Exception e) {
			if (isIgnorableException(e)) {
				if (e.getMessage().contains("Broken pipe") || e.getMessage().contains("Connection reset")) {
					log.derror("__write_to_socket:nio_copyable: Failed to write! [error: " + e + "]");
				} else {
					log.error("__write_to_socket:nio_copyable: Failed to write! [" + e + "]");
				}
			} else {
				log.error("__write_to_socket:nio_copyable: Failed to write!", e);
			}
			IOUtils.closeAllQuietly(response, socket, out, _os, is);
			__keep_alive.set(false);
			return;
		}
		try {
			if (os != null && !shouldUseChunked) {
				__finish_stream(os);
			}
		} catch (Exception e) {
			if ((e instanceof SocketException && e.getMessage().contains("Broken pipe")) || e instanceof SSLException || e instanceof SocketTimeoutException
					|| (e instanceof SocketException && e.getMessage().contains("Connection reset"))
					|| (e instanceof IOException && e.getMessage().contains("closed")) || e instanceof ConnectException) {
				IOUtils.closeAllQuietly(response, socket, out, _os, is);
				__keep_alive.set(false);
			} else
				log.error("__finish_stream: Failed to flush stream!", e);
			return;
		}
		__TIMING("__write_to_socket::copy-write:", start);
	}

	private void __copyable(final boolean shouldUseChunked, final Response response, final OutputStream os, final OutputStream _os,
			final HttpCompressionMethod comp, final Socket socket, final variable<Boolean> __keep_alive, final HTTPPrintWriter out, final InputStream is,
			final long start) throws Exception {
		try {
			if (shouldUseChunked) {
				this.__do_chunked_copyable(response.getCopyable(), (os != null ? os : _os), comp);
			} else {
				response.getCopyable().writeTo(os != null ? os : _os);
			}
		} catch (Exception e) {
			if (isIgnorableException(e)) {
				if (e.getMessage().contains("Broken pipe") || e.getMessage().contains("Connection reset")) {
					log.derror("__write_to_socket:copyable: Failed to write! [error: " + e + "]");
				} else {
					log.error("__write_to_socket:copyable: Failed to write! [" + e + "]");
				}
			} else {
				log.error("__write_to_socket:copyable: Failed to write!", e);
			}
			IOUtils.closeAllQuietly(response, socket, out, _os, is);
			__keep_alive.set(false);
			return;
		}
		try {
			if (os != null && !shouldUseChunked) {
				__finish_stream(os);
			}
		} catch (Exception e) {
			if (isIgnorableException(e)) {
				IOUtils.closeAllQuietly(response, socket, out, _os, is);
				__keep_alive.set(false);
				return;
			} else
				log.error("__write_to_socket:copyable: __finish_stream() failed.", e);
		}
		__TIMING("__write_to_socket::copy-write:", start);
	}

	private void __legacy_copy(final boolean __has_chunks, final ByteReadRange[] byteRanges, final Response response, final OutputStream os,
			final OutputStream _os, final HttpCompressionMethod comp, final boolean shouldUseChunked, final variable<Boolean> __keep_alive, final Socket socket,
			final HTTPPrintWriter out, final InputStream is, final long start) {
		try {
			if (__has_chunks) {
				__do_byte_ranges(byteRanges, response.getB(), os != null ? os : _os);
				__TIMING("__legacy_copy::byte-ranges:", start);
			} else {
				if (hs3.flags.enable_chunked_transfers && shouldUseChunked) {
					__do_chunked_transfer(response.getB(), (os != null ? os : _os), comp);
					__TIMING("__legacy_copy::chunked-transfer:", start);
				} else {
					copy(hs3.ccm, response.getB(), _os);
					__TIMING("__legacy_copy::copy:", start);
				}
			}
		} catch (Exception e) {
			if (e == null || e.getMessage() == null) { // Yes, I actually hit an exception where it somehow threw null.
			} else if (!isIgnorableException(e)) {
				log.error(e);
			}
			IOUtils.closeAllQuietly(response, socket, out, _os, is);
			__keep_alive.set(false);
			return;
		}
		__TIMING("__legacy_copy::uncomp-write:", start);
	}

	private void __fallback_copy(final boolean __has_chunks, final ByteReadRange[] byteRanges, final Response response, final OutputStream os,
			final boolean shouldUseChunked, final HttpCompressionMethod comp, final variable<Boolean> __keep_alive, final Socket socket,
			final HTTPPrintWriter out, final OutputStream _os, final InputStream is, final long start) throws Exception {
		try {
			if (__has_chunks) {
				__do_byte_ranges(byteRanges, response.getB(), os);
				__TIMING("__write_to_socket::fallback-write-range:", start);
			} else {
				if (shouldUseChunked) {
					__do_chunked_transfer(response.getB(), os, comp);
					__TIMING("__write_to_socket::fallback-write-chunked:", start);
				} else {
					copy(hs3.ccm, response.getInputStream(), os);
					__TIMING("__write_to_socket::fallback-write:", start);
				}
			}
			__TIMING("__write_to_socket::fallback-finish:", start);
			if (!shouldUseChunked) {
				__finish_stream(os);
			}
		} catch (Exception e) {
			if (e instanceof SocketException) {
				IOUtils.closeAllQuietly(response, socket, out, _os, is);
				__keep_alive.set(false);
				return;
			} else {
				log.error("__finish_stream: Failed to flush stream!", e);
			}
		}
	}

	private static void copy(final CopyAcceleration cc, final InputStream in, final OutputStream os) throws IOException {
		switch (cc) {
			case J9_COPY:
				TransferToBackport.transferTo(in, os);
				break;
			case FAST_COPY:
				IOUtils.fastCopy(in, os);
				break;
			default:
				IOUtils.copy(in, os);
				break;
		}
	}
}
